/**
 * 数学工具
 * @author 陈皮皮 (ifaswind)
 * @version 20201019
 * @see MathUtil.ts https://gitee.com/ifaswind/eazax-ccc/blob/master/utils/MathUtil.ts
 */
export default class MathUtil {

    /**
    * 获取一个 min 到 max 范围内的随机整数
    * @param min 最小值
    * @param max 最大值
    */
    public static getRandomInt(min: number = 0, max: number = 1): number {
        return Math.floor(Math.random() * (max - min) + min);
    }

    /**
     * 获取一个伪随机整数
     * @param seed 随机种子
     * @param key key
     */
    public static getPseudoRandomInt(seed: number, key: number): number {
        return Math.ceil((((seed * 9301 + 49297) % 233280) / 233280) * key);
    }

    /**
     * 获取两点间的角度
     * @param p1 点1
     * @param p2 点2
     */
    public static getAngle(p1: cc.Vec2, p2: cc.Vec2): number {
        return Math.atan((p2.y - p1.y) / (p2.x - p1.x));
    }

    /**
     * 获取两点间的距离
     * @param p1 点1
     * @param p2 点2
     */
    public static getDistance(p1: cc.Vec2, p2: cc.Vec2): number {
        return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
    }

    /**
     * 将角度转为弧度
     * @param angle 角度
     */
    public static angleToRadian(angle: number): number {
        return angle * Math.PI / 180;
    }

    /**
     * 浮点数加法运算（避免浮点数加法精度问题）
     * @param a 数
     * @param b 数
     */
    public static addSafely(a: number, b: number): number {
        const aDigits = (a.toString().split('.')[1] || '').length;
        const bDigits = (b.toString().split('.')[1] || '').length;
        const multiplier = Math.pow(10, Math.max(aDigits, bDigits));
        return (a * multiplier + b * multiplier) / multiplier;
    }

    /**
    * 获取两个向量的夹角
    * @param {cc.v2} comVec 向量1
    * @param {cc.v2} dirVec 向量2
    * @returns 
*/
    public static vectorsToDegree(comVec, dirVec) {
        // 求方向向量与对比向量间的弧度
        let radian = dirVec.signAngle(comVec);
        // 将弧度转换为角度
        let degree = cc.misc.radiansToDegrees(radian);
        if (degree < -90) {
            degree = -90 - (90 + degree);
        }

        if (degree > 90) {
            degree = 180 - degree;
        }
        return degree;
    }

    /**
  * 线性插值
  * @param a 起始值
  * @param b 目标值
  * @param r ratio between 0 and 1
  * @param min 最小间隔值
  */
    public static lerp(a: number, b: number, r: number, min: number = 0): number {
        min = Math.abs(min);
        let c = b - a;
        let delta = c * r;
        delta = delta < 0 ? Math.min(delta, -min) : Math.max(delta, min);
        if (Math.abs(delta) > Math.abs(c)) {
            delta = c;
        }
        return a + delta;
    }

    /**
     * 通过两点坐标(不平行于坐标轴)和x，计算两点式方程结果y
     */
    public static calcTwoPointForm(p1: cc.Vec2, p2: cc.Vec2, x: number): number {
        if (p1.x === p2.x) return p1.y;
        return (p2.y - p1.y) * (x - p1.x) / (p2.x - p1.x) + p1.y;
    }

    /**
     * 返回两个矩形的重叠矩形，不重叠则返回null
     */
    public static overlapRect(r1: cc.Rect, r2: cc.Rect): cc.Rect {
        let xMin = Math.max(r1.xMin, r2.xMin);
        let xMax = Math.min(r1.xMax, r2.xMax);
        let yMin = Math.max(r1.yMin, r2.yMin);
        let yMax = Math.min(r1.yMax, r2.yMax);

        if (xMin > xMax || yMin > yMax) {
            return null;
        }
        return cc.rect(xMin, yMin, xMax - xMin, yMax - yMin);
    }

    /**
     * 将角度约束在 [0,360) 区间内
     */
    public static normalizeDegree(degree: number): number {
        let result = degree % 360;
        if (result < 0) {
            result += 360;
        }
        return result;
    }

    /**
     * 圆心在坐标原点的椭圆，以与x轴逆时针方向的角度计算对应椭圆边上的坐标
     */
    public static getEllipsePoint(a: number, b: number, degree: number): cc.Vec2 {
        degree = this.normalizeDegree(degree);
        let k = Math.tan(cc.misc.degreesToRadians(degree));
        let x = Math.sqrt(b * b / (k * k + b * b / a / a));
        if (degree > 90 && degree < 270) {
            x = -x;
        }
        let y = Math.sqrt(b * b - b * b * x * x / a / a);
        if (degree > 180) {
            y = -y;
        }

        return cc.v2(x, y);
    }

    /**
     * 判断射线与圆是否相交
     * @param rayPoint 射线起点
     * @param rayDir 射线方向
     * @param circlrCenter 圆心
     * @param circleRadius 圆半径
     */
    public static isRayCircleIntersection(rayPoint: cc.Vec2 | cc.Vec3, rayDir: cc.Vec2, circlrCenter: cc.Vec2 | cc.Vec3, circleRadius: number): boolean {
        let d = rayDir.magSqr();
        let t = ((circlrCenter.x - rayPoint.x) * rayDir.x + (circlrCenter.y - rayPoint.y) * rayDir.y) / d;
        let p;
        if (d <= 0) {
            p = rayPoint;
        } else {
            if (t < 0) {
                p = rayPoint;
            } else {
                p = cc.v2(rayPoint.x + t * rayDir.x, rayPoint.y + t * rayDir.y);
            }
        }
        let dx = circlrCenter.x - p.x;
        let dy = circlrCenter.y - p.y;
        return (dx * dx + dy * dy) <= circleRadius * circleRadius;
    }

    /**
     * 返回value是否在 [min, max] 区间内
     * @param min 
     * @param max 
     * @param value
     * @param includeEdge true(默认值): [min, max]; false: (min, max)
     */
    public static inRange(min: number, max: number, value: number, includeEdge: boolean = true): boolean {
        return includeEdge ? value >= min && value <= max : value > min && value < max;
    }

    /**
     * 获取区间[min, max)的整数，传入1个参数则区间为[0, min)
     */
    public static randInt(min: number, max: number = undefined): number {
        if (max === undefined) {
            max = min;
            min = 0;
        }
        min = Math.ceil(min);
        max = Math.floor(max);
        return Math.floor(Math.random() * (max - min)) + min;
    }

    /**
     * 获取区间[min, max)的浮点数，传入1个参数则区间为[0, min)
     */
    public static randFloat(min: number, max: number = undefined): number {
        if (max === undefined) {
            max = min;
            min = 0;
        }
        return Math.random() * (max - min) + min;
    }

    /**
     * 返回一个概率值(%)的随机结果是否在概率范围内
     * @param rate 概率值(%) [0,100]
     */
    public static randRateResult(rate: number): boolean {
        return this.randFloat(100) < rate;
    }

    /**
     * 根据权重数组进行随机，返回结果下标
     * @param weightArr 权重数组
     * @returns 随机到的权重数组下标
     */
    public static randWeightIdx(weightArr: number[]) {
        let sum = 0;
        for (let i = 0; i < weightArr.length; i++) {
            sum += weightArr[i];
        }
        let randNum = this.randFloat(0, sum);
        let curValue = 0
        for (let i = 0; i < weightArr.length; i++) {
            curValue += weightArr[i];
            if (randNum < curValue) {
                return i;
            }
        }
        return weightArr.length - 1;
    }

    /**
     * Fisher–Yates shuffle 字符串随机乱序
     */
    public static shuffleString(str: string): string {
        let arr: string[] = [];
        for (let i = 0; i < str.length; i++) {
            arr.push(str[i]);
        }
        arr = this.shuffle(arr);
        str = "";
        arr.forEach((v) => {
            str += v;
        });
        return str;
    }

    /**
     * Fisher–Yates shuffle 数组随机乱序
     */
    public static shuffle<T>(arr: Array<T>): Array<T> {
        for (let i = arr.length - 1; i >= 0; i--) {
            let randomIndex = Math.floor(Math.random() * (i + 1));
            [arr[randomIndex], arr[i]] = [arr[i], arr[randomIndex]];
        }
        return arr;
    }

    /**
     * 随机返回数组中的一个元素
     */
    public static arrayRand<T>(arr: Array<T>): T {
        if (arr.length <= 0) {
            return null;
        }
        return arr[this.randInt(0, arr.length)];
    }

    /**
     * 判断数组中是否有某个元素
     * @param arr 数组
     * @param param 元素值或表达元素值满足某种条件的函数
     */
    public static arrayHas<T>(arr: T[], param: T | ((ele: T) => boolean)): boolean {
        let idx = typeof param !== "function" ? arr.findIndex((e) => { return e === param; }) : arr.findIndex(param as ((ele: T) => boolean));
        return idx >= 0;
    }

    /**
     * 根据下标交换数组两个元素位置
     */
    public static arraySwap<T>(arr: T[], idx1: number, idx2: number): void {
        if (idx1 === idx2 || !this.inRange(0, arr.length - 1, idx1) || !this.inRange(0, arr.length - 1, idx2)) {
            return;
        }
        [arr[idx1], arr[idx2]] = [arr[idx2], arr[idx1]];
    }

    /**
     * 将元素从fromIdx位置移到toIdx位置，其余元素相对位置不变
     */
    public static arrayMove<T>(arr: T[], fromIdx: number, toIdx: number): void {
        if (fromIdx === toIdx || !this.inRange(0, arr.length - 1, fromIdx) || !this.inRange(0, arr.length - 1, toIdx)) {
            return;
        }
        let from: T[] = arr.splice(fromIdx, 1);
        arr.splice(toIdx, 0, from[0]);
    }

    /**
     * 在数组中添加某个元素
     * @param canRepeat 是否可重复添加相同元素 默认false
     * @return 是否执行了添加行为
     */
    public static arrayAdd<T>(arr: T[], ele: T, canRepeat: boolean = false): boolean {
        if (!canRepeat && this.arrayHas(arr, ele)) {
            return false;
        }
        arr.push(ele);
        return true;
    }

    /**
     * 在数组中删除某个元素(若有多个相同元素则只删除第一个)
     * @return 是否执行了删除行为
     */
    public static arrayDelete<T>(arr: T[], ele: T): boolean {
        let index: number = arr.findIndex((e) => { return e === ele; });
        if (index >= 0) {
            arr.splice(index, 1);
            return true;
        } else {
            return false;
        }
    }
    /**
     * 数组中最小值
     */
    public static arrGetMin<T>(arr: T[]): T {
        let min = arr[0];
        for (var i = 1; i < arr.length; i++) {
            var cur = arr[i];
            cur < min ? min = cur : null
        }
        return min
    }
    public static segmentsIntr(a, b, c, d){  
        var nx1 = (b.y - a.y), ny1 = (a.x - b.x);  

        //线段cd的法线N2  
        var nx2 = (d.y - c.y), ny2 = (c.x - d.x);  

        //两条法线做叉乘, 如果结果为0, 说明线段ab和线段cd平行或共线,不相交  
        var denominator = nx1*ny2 - ny1*nx2;  
        if (denominator==0) {  
            return false;  
        }  

        //在法线N2上的投影  
        var distC_N2=nx2 * c.x + ny2 * c.y;  
        var distA_N2=nx2 * a.x + ny2 * a.y-distC_N2;  
        var distB_N2=nx2 * b.x + ny2 * b.y-distC_N2;  

        // 点a投影和点b投影在点c投影同侧 (对点在线段上的情况,本例当作不相交处理);  
        if ( distA_N2*distB_N2>=0  ) {  
            return false;  
        }  

        //  
        //判断点c点d 和线段ab的关系, 原理同上  
        //  
        //在法线N1上的投影  
        var distA_N1=nx1 * a.x + ny1 * a.y;  
        var distC_N1=nx1 * c.x + ny1 * c.y-distA_N1;  
        var distD_N1=nx1 * d.x + ny1 * d.y-distA_N1;  
        if ( distC_N1*distD_N1>=0  ) {  
            return false;  
        }  

        //计算交点坐标  
        var fraction= distA_N2 / denominator;  
        var dx= fraction * ny1,  
            dy= -fraction * nx1;  
        return { x: a.x + dx , y: a.y + dy };    


        }  
    // public static segmentsIntr(a, b, c, d) {

    //     /** 1 解线性方程组, 求线段交点. **/
    //     // 如果分母为0 则平行或共线, 不相交  
    //     var denominator = (b.y - a.y) * (d.x - c.x) - (a.x - b.x) * (c.y - d.y);
    //     if (denominator == 0) {
    //         return false;
    //     }

    //     // 线段所在直线的交点坐标 (x , y)      
    //     var x = ((b.x - a.x) * (d.x - c.x) * (c.y - a.y)
    //         + (b.y - a.y) * (d.x - c.x) * a.x
    //         - (d.y - c.y) * (b.x - a.x) * c.x) / denominator;
    //     var y = -((b.y - a.y) * (d.y - c.y) * (c.x - a.x)
    //         + (b.x - a.x) * (d.y - c.y) * a.y
    //         - (d.x - c.x) * (b.y - a.y) * c.y) / denominator;

    //     /** 2 判断交点是否在两条线段上 **/
    //     if (
    //         // 交点在线段1上  
    //         (x - a.x) * (x - b.x) <= 0 && (y - a.y) * (y - b.y) <= 0
    //         // 且交点也在线段2上  
    //         && (x - c.x) * (x - d.x) <= 0 && (y - c.y) * (y - d.y) <= 0
    //     ) {

    //         // 返回交点p  
    //         return {
    //             x: x,
    //             y: y
    //         }
    //     }
    //     //否则不相交  
    //     return false

    // }
}
